﻿using System;
using System.Collections;
using System.Runtime.InteropServices;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;

namespace LowLevelGraphics.Drawing.FloodFillModes
{
    /// <summary>
    /// Fills a bitmap using a non-recursive flood-fill.
    /// </summary>
    public class MapFill : AbstractFloodFill
    {
        private static Stack stack = new Stack();

        /// <summary>
        /// Initializes a new instance of the <see cref="MapFill"/> class.
        /// </summary>
        public MapFill()
        {
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="MapFill"/> class.
        /// </summary>
        /// <param name="_mapFill">The _map fill.</param>
        internal MapFill(MapFill _mapFill)
        {
        }     

        /// <summary>
        /// Checks to make sure a pixel is in an image.
        /// </summary>
        /// <param name="pos">The position to check</param>
        /// <param name="bmd">The BitmapData from which the bounds are determined</param>
        /// <returns>True if the point is in the image</returns>
        private static bool CheckPixel(Point pos, BitmapData bmd)
        {
            return (pos.X > -1) && (pos.Y > -1) && (pos.X < bmd.Width) && (pos.Y < bmd.Height);
        }

        /// <summary>
        /// Returns the color at a specific pixel
        /// </summary>
        /// <param name="pos">The position of the pixel</param>
        /// <param name="bmd">The locked bitmap data</param>
        /// <returns>The color of the pixel under the nominated point</returns>
        private static Color GetPixel(Point pos, BitmapData bmd)
        {
            if (CheckPixel(pos, bmd))
            {
                //always assumes 32 bit per pixels
                int offset = pos.Y * bmd.Stride + (4 * pos.X);
                return Color.FromArgb(
                    Marshal.ReadByte(bmd.Scan0, offset + 2),
                    Marshal.ReadByte(bmd.Scan0, offset + 1),
                    Marshal.ReadByte(bmd.Scan0, offset));
            }
            else
                return Definitions.Black;
        }

        /// <summary>
        /// Sets a pixel at a nominated point to a specified color
        /// </summary>
        /// <param name="pos">The coordinate of the pixel to set</param>
        /// <param name="bmd">The locked bitmap data</param>
        /// <param name="c">The color to set</param>
        private static void SetPixel(Point pos, BitmapData bmd, Color c)
        {
            if (CheckPixel(pos, bmd))
            {
                //always assumes 32 bit per pixels
                int offset = pos.Y * bmd.Stride + (4 * pos.X);
                Marshal.WriteByte(bmd.Scan0, offset + 2, c.R);
                Marshal.WriteByte(bmd.Scan0, offset + 1, c.G);
                Marshal.WriteByte(bmd.Scan0, offset, c.B);
                Marshal.WriteByte(bmd.Scan0, offset + 3, 255);
            }
        }

        /// <summary>
        /// Fills a pixel and its un-filled neigbors with a specified color
        /// </summary>
        /// <param name="pos">The position at which to begin</param>
        /// <param name="bmd">The locked bitmap data</param>
        /// <param name="c">The color with which to fill the area</param>
        /// <param name="org">The original colour of the point. Filling stops when all connected pixels of this color are exhausted</param>
        private static void FillPixel(Point pos, BitmapData bmd, Color c, Color org)
        {
            Point currpos = new Point(0, 0);
            stack.Push(pos);
            do
            {
                currpos = (Point)stack.Pop();
                SetPixel(currpos, bmd, c);
                if (GetPixel(new Point(currpos.X + 1, currpos.Y), bmd) == org)
                    stack.Push(new Point(currpos.X + 1, currpos.Y));
                if (GetPixel(new Point(currpos.X, currpos.Y - 1), bmd) == org)
                    stack.Push(new Point(currpos.X, currpos.Y - 1));
                if (GetPixel(new Point(currpos.X - 1, currpos.Y), bmd) == org)
                    stack.Push(new Point(currpos.X - 1, currpos.Y));
                if (GetPixel(new Point(currpos.X, currpos.Y + 1), bmd) == org)
                    stack.Push(new Point(currpos.X, currpos.Y + 1));
            } while (stack.Count > 0);
        }

        /// <summary>
        /// Fills a bitmap with color.
        /// </summary>
        /// <remarks>If a non 32-bit image is passed to this routine and only 32 bit image will be created, the original image will be copied to the new image and filling will take place on the new image which will be handed back when complete.   </remarks>
        /// <param name="img">The image to fill</param>
        /// <param name="pos">The position to begin filling at</param>
        /// <param name="color">The color to fill</param>
        /// <returns>A Bitmap object with the filled area.</returns>
        public static UnsafeBitmap Fill(UnsafeBitmap img, Point pos, Color color)
        {
            //Ensure the bitmap is in the right format
            UnsafeBitmap bm = img;
            if (img.PixelFormat != PixelFormat.Format32bppArgb)
            {
                //if it isn't, convert it.
                bm = new Bitmap(img.Width, img.Height, PixelFormat.Format32bppArgb);
                //Graphics g = Graphics.FromImage(bm);
                //g.InterpolationMode = InterpolationMode.NearestNeighbor;
                //g.DrawImage(img, new Rectangle(0, 0, bm.Width, bm.Height), 0, 0, img.Width, img.Height, GraphicsUnit.Pixel);
                //g.Dispose();
            }

            //Lock the bitmap data
            BitmapData bmd = bm.BitmapData;//bm.LockBits(new Rectangle(0, 0, bm.Width, bm.Height), ImageLockMode.ReadWrite, bm.PixelFormat);

            //get the color under the point. This is the original.
            Color org = GetPixel(pos, bmd);

            //Fill the first pixel and recursively fill all it's neighbors
            FillPixel(pos, bmd, color, org);

            //unlock the bitmap
            bm.Dispose();

            return bm;
        }

        /// <summary>
        /// Fills the specified _bitmap.
        /// </summary>
        /// <param name="_bitmap">The _bitmap.</param>
        /// <param name="_nX">The _n X.</param>
        /// <param name="_nY">The _n Y.</param>
        /// <param name="_color">The _color.</param>
        public override void Fill(UnsafeBitmap _bitmap, int _nX, int _nY, Color _color)
        {
            Fill(_bitmap, new Point(_nX, _nY), _color);
        }

        /// <summary>
        /// Creates a new object that is a copy of the current instance.
        /// </summary>
        /// <returns>
        /// A new object that is a copy of this instance.
        /// </returns>
        public override object Clone()
        {
            return new MapFill(this);
        }
    }
}
